閱讀前,建議可以參考Day1:閱讀指南&為何選擇這個題目?
題目:計算機概論X30天
挑戰內容:連續30天紀錄計算機概論、離散數學、演算法、資料結構等課程,還有自己學習程式的心得體悟。
適合閱讀者:稍微會一點程式(最好是JS),知道什麼是迴圈、變數的入門,然後想知道如何分析程式的時間複雜度
不適合閱讀者:連程式都沒寫過、不知道什麼是迴圈、變數的超級超級外行&寫程式多年,對演算法、數據結構、計算機解透徹的高手。(因為這是我初學的心得,因此內容會可能過於簡單且不夠嚴謹。)
要判斷一個程式是否有效率,首先要定義何謂「有效率」。
一個有效率的程式有主要兩個特色:
本篇討論運行時間
這件事好了,要怎麼知道運行時間?很簡單啊——把程式跑一遍,然後就知道時間了
太聰明了!沒錯。
以為我要阻止你嗎?沒有。
想知道a和b兩個程式哪個快,當然就是跑一遍囉!
很多程式上線之前也會做測試,因此直接測試當然是個方式。
But,但是,但是
實際測試會有一些限制,那就是很依賴測試環境
還有數據狀況
同一個程式:
不同處理器
消耗的時間可能不ㄧ樣(用a處理器可能耗費0.01秒,b處理器可能0.02)不同電腦
跑出的時間不ㄧ樣(用a電腦可能耗費0.01秒,b電腦可能0.02)不同數量
的數據耗費的時間不ㄧ樣(輸入1000000個Data,跟輸入1個Data耗費的時間不一樣)不同狀況的數據
耗費的時間不ㄧ樣(比如說如果想把數列從小排到大,那處理1,2,3,4跟4,3,2,1兩筆資料所耗費的時間不ㄧ樣,前者一定比較快,因為根本不用做什麼就已經是小排到大了)因此,除了實際運行外,我們似乎還是需要一個不受環境、數據狀況
能夠客觀描述程式的大概會耗費多少時間資源的方式這時候就需要——時間複雜度(time complexity)
分析
時間複雜度(time complexity)分析是什麼?它是一種不依賴直接運行程式,只要用肉眼觀察程式碼、掐掐拇指,然後大致可以計算出程式會耗費的時間資源的分析方式。
要怎麼做呢?看以下範例
假設電腦每運行一行程式
會耗費1個單位時間t
時間單位t的由各別電腦效能決定,可能是0.0000001或是0.000000001(看不同電腦)
我們來算算運行以下這些程式大概會花多少時間。。(以下用Javascript做範例)
function f1() {
var a = 0 //耗費一個時間單位t
console.log(a) //耗費一個時間單位t
}
這個function f1函式消耗的t+t=2t,也就是2個時間單位
function f2() {
var a = 0 //耗費一個時間單位(t)
for (var i = 0; i < 10; i++) {
a++; //耗費10個時間單位(t),因為重複了10次
}
console.log(a); //耗費一個時間單位t
}
這個function f2函式消耗了10t+2t=12t,共12個時間單位
function f3(n) {
var a = 0 //耗費一個時間單位(t)
for (var i = 0; i < n; i++) {
a++; //耗費n個時間單位(t),n是什麼由這個函式吃到什麼數據決定
}
console.log(a); //耗費一個時間單位(t)
}
這個function f3函式共耗費nt+2t=(n+2)t,共(n+2)個時間單位
function f4(n) {
var a = 0; //耗費一個時間單位(t)
for (let i = 0; i < n; i++) { //耗費n個時間單位(t)
for (let i = 0; i < n; i++) { //耗費n個時間單位(t)
a++;
}
}
console.log(a); //耗費一個時間單位(t)
}
這個function f4函式共耗費(nxn)+2=(n^2+2)t,也就是共n^2+2個時間單位
整理一下
function f1:消耗了2t(時間單位)
function f2:消耗了12t(時間單位)
function f3:消耗了(n+2)t(時間單位)
function f4:消耗了(n^2+2)t(時間單位)
運行速度是f1>f2,至於f3、f4速度則要看n的大小決定
OK,那要怎麼客觀形容程式耗費的時間資源
呢?傳統上會轉換成一個BigO
函示來表示程式的時間複雜度
代換之後,變成如下
function f1:消耗了2t(時間單位) => 時間複雜度是O(1)
function f2:消耗了12t(時間單位) => 時間複雜度是O(1)
function f3:消耗了(n+2)t(時間單位) => 時間複雜度是O(n)
function f4:消耗了(n^2+2)t(時間單位) => 時間複雜度是O(n^2)
可能會覺得很怪,為何f1、f2的時間複雜度相同?為何f3時間複雜度是O(n)?為何f4時間複雜度O(n^2)?
這邊要澄清一個觀念
O(n)不代表表執行時間
,它代表的是執行時間隨著數據規模增長的變化趨勢
因此Big O又稱為漸進時間複雜度
function f1:消耗了2t(時間單位) => 時間複雜度是O(1)
function f2:消耗了12t(時間單位) => 時間複雜度是O(1)
由於O(n)代表的是執行時間隨著數據規模增長
的變化趨勢,但對於f1、f2來說,這個程式根本沒有數據規模增長的問題。因此對電腦來說不管是2t還是12t,都是同個量級的程式,因此時間複雜度是O(1)
function f3:消耗了(n+2)t(時間單位) => 時間複雜度是O(n)
f3這個函式有數據規模增長的問題,當輸入的n愈大時,它消耗的時間愈多
那為什麼不是O(n+2)?
因為當n很大時(比如10萬),那個2可以被忽略計算,因此會省略2直接寫成O(n)
function f4:消耗了(n^2+2)t(時間單位) => 時間複雜度是O(n^2)
為什麼時間複雜度不是O(n^2+2)?
理由跟上面ㄧ樣,當n很大(比如10萬),那個2基本就是個零頭,因此會省略直接寫成O(n^2+)
從上面可以知道,函式消耗的時間單位不管是
它們的時間複雜度也可以寫成O(n)
可是,100000很大耶!n+100000個時間單位跟耗費n個時間單位明明就差很多!
沒錯!所以再次澄清一遍,O(n)不代表執行時間
,它代表的是執行時間隨著數據規模增長
的變化趨勢
在n很小的時候或許n+1跟n+100000執行時間或許有差,但是在n很大很大很大的時候n+1跟n+100000的運行時間是差不多的。
由於Big O觀察的是隨著數據規模增長消耗時間的變化趨勢
我們得到一個結論「只要關注那些影響時間變化最多的程式即可」
因此有一些技巧
function f6(n) {
var a = 0; //可以直接省略
for (let i = 0; i < n; i++) {
a++; //這個重複最多次,關注他就好了
}
console.log(a); //可以直接省略
console.log("妳好"); //可以直接省略
console.log("安安"); //可以直接省略
console.log("給虧嗎") //可以直接省略
}
這個function f6函式雖然耗費n+5個單位時間,但是複雜度依然是寫作O(n)
那如果遇到下面的狀況呢
function f7(n) {
//a區塊
var a = 0;
for (let i = 0; i < n; i++) {
a++;
}
console.log(a);
//b區塊
var b = 0;
for (let i = 0; i < n; i++) {
for (let i = 0; i < n; i++) {
b++;
}
}
console.log(b);
}
在這個函式中,a區塊的複雜度是O(n),b區塊是O(n^2),這時候要怎麼辦?
答案是——直接省略a區塊的O(n),該function f7函數的複雜度是O(n^2)
原因是什麼?
跟之前的邏輯一樣,當n很大的時候,n^2會遠遠大於n,使得n可以被忽略不計。
不逃脫以下幾種
後兩者的複雜度很糟糕,因為當n愈大時,他們複雜度上升的速度非常快(意思是說運行效率就愈低),因此是糟糕的算法。他們稱為NP非多項是量級算法。
從上圖可以看不同算法,當Input數據愈大時,複雜度的增長趨勢
・極客時間APP・《數據結構與算法之美》:前面兩個章節的學習心得
老師用Java寫,我改用JS寫並且順便省略一堆東西。因此如果對這個議題有興趣,可直接訂閱該專欄,不用看我這個菜鳥的心得(但你看完了哈哈)
*終於進入了一些技術、理論的部分(呼)
*每天寫那麼多字,頭真的有點暈,開始無法認真檢查了。有任何的錯誤歡迎指正QQ(鞠躬)